/*** syncnshare version 4.0
This version makes changes in directories (copy, delete...).
Call this program and provide two folders to syncronize
RAFAEL PALACIOS Feb/2011
***/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

//#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <time.h>
#include <libgen.h>  //for basename function

typedef struct {
	char full_filename[PATH_MAX];
	off_t st_size; //file size in bytes
	time_t st_mtimesec; //Modify time
} FILE_INFO;

typedef struct {
	int num_files; //number of files in the directory
	int max_files; //space allocated for *info
	char prefix[PATH_MAX]; //path prefix
	FILE_INFO *info; //pointer to the files. This pointer requieres malloc.
} DIR_INFO;
        
        
DIR_INFO ReadLocalDirFile(char myDir[]);
DIR_INFO ListDirectory(char prefix[], char myDir[]);

void Analyze(DIR_INFO dir_old,DIR_INFO dir_local,DIR_INFO dir_remote);
void WriteLocalDirFile(char myDir[],DIR_INFO dir_new);
void Display(DIR_INFO dir_tmp);
void Upload(DIR_INFO dir_local,DIR_INFO dir_remote,int k_local);
void Download(DIR_INFO dir_local,DIR_INFO dir_remote,int k_remote);
void DeleteLocal(DIR_INFO dir_local,int k_local);
void DeleteRemote(DIR_INFO dir_remote,int k_remote);


int main( int argc, char **argv )
{
	DIR_INFO dir_old;
	DIR_INFO dir_local;
	DIR_INFO dir_remote;
	DIR_INFO dir_new;
	
	/* Initialize */
	dir_old.info=NULL;
	dir_local.info=NULL;
	dir_remote.info=NULL;
	dir_new.info=NULL;
	
	
	if (argc!=3) {
		perror("Version 4.0 - Usage: syncnshare local_dir remote_dir\n");
		exit(1);
	}
	
	//Read all the information
	dir_old=ReadLocalDirFile(argv[1]);
	dir_local=ListDirectory(argv[1],"");
	dir_remote=ListDirectory(argv[2],"");
	
	//Analyze what to do
	if (dir_remote.num_files>1 || dir_old.num_files<1) { //This is to avoid remote mounting problems
		Analyze(dir_old,dir_local,dir_remote);
	
		//Update the local dir file
		dir_new=ListDirectory(argv[2],"");  //read again ??
		printf("\nWrite to file:\n");
		WriteLocalDirFile(argv[1],dir_new);
	}
	
	
	//Display the contents (to check if everything is ok)
	//printf("Old local file:\n");
	//Display(dir_old);
	//printf("Local dir:\n");
	//Display(dir_local);
	//printf("Remote dir:\n");
	//Display(dir_remote);
	//printf("Updated remote dir (again):\n");
	//Display(dir_new);
	
	
	//Free memory
	if (dir_old.info != NULL) free(dir_old.info);
	if (dir_local.info != NULL) free(dir_local.info);
	if (dir_remote.info != NULL) free(dir_remote.info);
	if (dir_new.info != NULL) free(dir_new.info);
	
	printf("\nEnd of program.\n");
	
    return EXIT_SUCCESS;
}



DIR_INFO ListDirectory(char prefix[], char myDir[])
{
	DIR_INFO res; //result
	DIR_INFO tmp; //for recursive call
	FILE_INFO tmp_file; //for storing in DIR_INFO

    DIR *dir = NULL;
    struct dirent *entry;
	struct stat entryInfo;
    char pathName[PATH_MAX];
    char FullpathName[PATH_MAX];
        
	/* Initialize res */
	res.num_files=0;
	res.max_files=0;
	res.info=NULL;
        
	/*** Analyze prefix ***/
	strncpy( res.prefix, prefix, PATH_MAX );
	if (prefix[strlen(prefix)-1]!='/') {		//For DOS \\ instead of /
		strncat( res.prefix, "/", PATH_MAX );	//For DOS \\ instead of /
	}
	
    /*** Begin reading main directory ***/
	strncpy( pathName, res.prefix, PATH_MAX );
	strncat( pathName, myDir, PATH_MAX );
    dir = opendir( pathName );
    if( dir == NULL ) {
        printf( "Error opening %s: %s", pathName, strerror( errno ) );
        exit(1);
    }
    
	/*** Read directory loop (with recursive call) ***/
	entry=readdir(dir);
    while( entry != NULL ) {
        
        /* Skip . and .. entries */
        if ( // ( strncmp( entry->d_name, ".", PATH_MAX ) == 0 ) ||  I also store filename . to make/remove directories
		    ( strncmp( entry->d_name, "..", PATH_MAX ) == 0 ) ||
			( entry->d_name[0]=='.' && entry->d_name[1]!='\0') ) {
				entry=readdir(dir);
				continue;
        }
                
		/*** Build path names ***/
		strncpy( pathName, "", PATH_MAX );
        if (myDir[0]!='\0') {
			strncat( pathName, myDir, PATH_MAX );
			strncat( pathName, "/", PATH_MAX );		//For DOS \\ instead of /
		}
        strncat( pathName, entry->d_name, PATH_MAX );
		
        strncpy( FullpathName, res.prefix, PATH_MAX );
        strncat( FullpathName, pathName, PATH_MAX );

		
		/*** Spacial case of filename . for directories ***/
        if (strncmp( entry->d_name, ".", PATH_MAX ) == 0) {
			/* Save data */
			if (res.num_files + 1 > res.max_files) {
				//We need to realloc
				res.info=(FILE_INFO *)realloc(res.info, sizeof(FILE_INFO)*(res.num_files + 100)); //more efficient in steps of 100
				if (res.info==NULL) {
					perror("Not enough memory. Abort.\n");
					exit(1);
				}
				res.max_files = res.max_files + 100;
			}
			//Add file data
			strncpy(tmp_file.full_filename,pathName,PATH_MAX);
			tmp_file.st_size = 1;  //no size
			tmp_file.st_mtimesec = 1;  //no time
			
			res.info[res.num_files]=tmp_file;
			res.num_files ++;

			entry=readdir(dir);
			continue;
		}			
		
		
		/*** For normal files we obtain more info with lstat ***/
        if( stat( FullpathName, &entryInfo ) == 0 ) {
            /* stat() succeeded, this could be the slowest function code */
            
            if( S_ISDIR( entryInfo.st_mode ) ) { /************ Directory. Recursive call *************/
				strncpy( pathName, "", PATH_MAX );
				if (myDir[0]!='\0') {
					strncat( pathName, myDir, PATH_MAX );
					strncat( pathName, "/", PATH_MAX );		//For DOS \\ instead of /
				}
				strncat( pathName, entry->d_name, PATH_MAX );
				tmp=ListDirectory( res.prefix, pathName );
				
				//Add subdirectory listings to current listings
				if (res.num_files + tmp.num_files > res.max_files) {
						//We need to realloc
						res.info=(FILE_INFO *)realloc(res.info, sizeof(FILE_INFO)*(res.num_files + tmp.num_files));
						if (res.info==NULL) {
								perror("Not enough memory. Abort.\n");
								exit(1);
						}
						res.max_files = res.num_files + tmp.num_files;
				}		
				//Merge data
				memcpy(&res.info[res.num_files], tmp.info, sizeof(FILE_INFO)*tmp.num_files);
				res.num_files += tmp.num_files;
				
				//Free tmp
				free(tmp.info);
				tmp.info=NULL;
				tmp.max_files=0;
				tmp.num_files=0;
                                                                
            } else if( S_ISREG( entryInfo.st_mode ) ) { /************  regular file *************/
                                
				/* Save data */
				if (res.num_files + 1 > res.max_files) {
						//We need to realloc
						res.info=(FILE_INFO *)realloc(res.info, sizeof(FILE_INFO)*(res.num_files + 100)); //more efficient in steps of 100
						if (res.info==NULL) {
								perror("Not enough memory. Abort.\n");
								exit(1);
						}
						res.max_files = res.max_files + 100;
				}
				//Add file data
				strncpy(tmp_file.full_filename,pathName,PATH_MAX);
				tmp_file.st_size = entryInfo.st_size;
				tmp_file.st_mtimesec = entryInfo.st_mtime;
				//tmp_file.st_ctimesec = entryInfo.st_ctime;
				
				res.info[res.num_files]=tmp_file;
				res.num_files ++;
                                
 //           } else if( S_ISLNK( entryInfo.st_mode ) ) { /************  symbolic link *************/
 //                               printf("%s is a link. Ignored.",pathName);
            
			} else {
				printf( "Error statting %s: %s\n", pathName, strerror( errno ) );
			}
        }        
		entry=readdir(dir);
    }
    
    /* Close the directory and return the number of entries. */
    closedir( dir );
        
    return res;
}

void Analyze(DIR_INFO dir_old,DIR_INFO dir_local,DIR_INFO dir_remote)
{
	time_t dt_old, dt_local, dt_remote;
	int k_old, k_local, k_remote;
	int type;
	
	k_old=k_local=k_remote=0;
	while(k_old<dir_old.num_files || k_local<dir_local.num_files || k_remote<dir_remote.num_files)  {
		//Any list is empty?
		dt_old=dt_local=dt_remote=0;
		if (k_old<dir_old.num_files) dt_old=dir_old.info[k_old].st_mtimesec;
		if (k_local<dir_local.num_files) dt_local=dir_local.info[k_local].st_mtimesec;
		if (k_remote<dir_remote.num_files) dt_remote=dir_remote.info[k_remote].st_mtimesec;
		
		//Missing files by comparing names?
		if (dt_old>0    && dt_local>0)  if (strcmp(dir_old.info[k_old].full_filename,       dir_local.info[k_local].full_filename) < 0)   dt_local=0;
		if (dt_old>0    && dt_remote>0) if (strcmp(dir_old.info[k_old].full_filename,       dir_remote.info[k_remote].full_filename) < 0) dt_remote=0;
		if (dt_local>0  && dt_old>0)    if (strcmp(dir_local.info[k_local].full_filename,   dir_old.info[k_old].full_filename) < 0)       dt_old=0;
		if (dt_local>0  && dt_remote>0) if (strcmp(dir_local.info[k_local].full_filename,   dir_remote.info[k_remote].full_filename) < 0) dt_remote=0;
		if (dt_remote>0 && dt_old>0)    if (strcmp(dir_remote.info[k_remote].full_filename, dir_old.info[k_old].full_filename) < 0)       dt_old=0;
		if (dt_remote>0 && dt_local>0)  if (strcmp(dir_remote.info[k_remote].full_filename, dir_local.info[k_local].full_filename) < 0)   dt_local=0;
		
		//determine type
		type = (dt_local > 0) << 2 | (dt_old > 0) << 1 | (dt_remote > 0);
		switch (type) {
			case 0: // 0 0 0
				printf("0 0 0: End of processing\n");
				break;
			case 1: // 0 0 1: Another user uploaded a new file to the system
				Download(dir_local,dir_remote,k_remote);
				k_remote++;
				break;
			case 2: // 0 1 0: File was deleted from server and local simultaneously, just update dir_old 
				k_old++;
				break;
			case 3: // 0 1 1: File was deleted from local. Compare dates
				if (dt_old == dt_remote) DeleteRemote(dir_remote,k_remote);
				if (dt_old < dt_remote) Download(dir_local,dir_remote,k_remote);
				if (dt_old > dt_remote) printf("Error: server file updated with older version\n   %s\n",dir_remote.info[k_remote].full_filename);
				k_remote++;
				k_old++;
				break;
			case 4: // 1 0 0: New file was created in local
				Upload(dir_local,dir_remote,k_local);
				k_local++;
				break;
			case 5: // 1 0 1: New file was created in local and server at the same time.
				printf("Conflict: \n   %s\n   %s\n",
					   dir_remote.info[k_remote].full_filename,
					   dir_local.info[k_local].full_filename);
				k_local++;
				k_remote++;
				break;
			case 6: // 1 1 0: File was deleted from server (by another user). Check dates
				if (dt_local == dt_old) DeleteLocal(dir_local,k_local);
				if (dt_local < dt_old)  printf("Error: local file updated with older version\n   %s\n",dir_local.info[k_local].full_filename);
				if (dt_local > dt_old)  Upload(dir_local,dir_remote,k_local);
				k_local++;
				k_old++;
				break;
			case 7: // 1 1 1: File present everywhere. Check dates
				if (dt_local == dt_old && dt_old == dt_remote) printf("Do nothing. Everything in sync: %s\n",dir_local.info[k_local].full_filename);
				if (dt_local == dt_old && dt_local < dt_remote) Download(dir_local,dir_remote,k_remote);
				if (dt_local == dt_old && dt_local > dt_remote) printf("Error: server file updated with older version\n   %s\n",dir_remote.info[k_remote].full_filename);
				if (dt_local < dt_old) printf("Error: local file updated with older version\n   %s\n",dir_local.info[k_local].full_filename);
				if (dt_local > dt_old && dt_old == dt_remote) Upload(dir_local,dir_remote,k_local);
				if (dt_local == dt_remote && dt_local < dt_old) printf("Local file is wrong\n");
				if (dt_local == dt_remote && dt_local > dt_old) printf("Local file is wrong\n");
				if (dt_local > dt_old && dt_old > dt_remote) printf("Error: server file updated with older version\n   %s\nand local file updated.\n",dir_remote.info[k_remote].full_filename);
				if (dt_local > dt_old && dt_old < dt_remote) printf("Conflict. File updated at local and server %s\n",dir_local.info[k_local].full_filename);
				k_local++;
				k_old++;
				k_remote++;
				break;
				
			default:
				printf("Imposible type: %d",type);
				break;
		}
		
		
		
	}
}

DIR_INFO ReadLocalDirFile(char myDir[])
{
        FILE *fp;
        char filename[PATH_MAX];
        DIR_INFO res; //result

        /* Initialize res */
        res.num_files=0;
        res.max_files=0;
        res.info=NULL;

        
        strncpy(filename,myDir,PATH_MAX-1);
        strncat(filename,"/.syncnshare.info",PATH_MAX-1);
        fp=fopen(filename,"rb");
        if (fp==NULL) {
                //File does not exist
                printf("This is your first sync process\n");
        } else {
                fread(&res.num_files,sizeof(res.num_files),1,fp);
                if (res.num_files != 0)  {
                        res.info=(FILE_INFO *)malloc(sizeof(FILE_INFO)*res.num_files);
                        if (res.info==NULL) {
                                perror("Not enough memory. Abort.\n");
                                exit(1);
                        }
                        res.max_files=res.num_files;
                        fread(res.info,sizeof(FILE_INFO),res.num_files,fp);
                }
                fclose(fp);
        }
        return res;
}

void WriteLocalDirFile(char myDir[],DIR_INFO dir_new)
{
        FILE *fp;
        char filename[PATH_MAX];

        strncpy(filename,myDir,PATH_MAX);
        strncat(filename,"/.syncnshare.info",PATH_MAX);
        fp=fopen(filename,"wb");
        if (fp==NULL) {
                perror("Error in Free-sync. Cannot create .syncnshare.info\n");
                exit(1);
        } else {
                fwrite(&dir_new.num_files,sizeof(dir_new.num_files),1,fp);
                fwrite(dir_new.info,sizeof(FILE_INFO),dir_new.num_files,fp);
                fclose(fp);
        }
}

void Display(DIR_INFO dir_tmp)
{
        int i;
        char st1[100],st2[100];

        printf("%d files:\n",dir_tmp.num_files);
        /* Display listing on the screen */
        for (i=0; i<dir_tmp.num_files; i++) {
                        
                strcpy(st1,asctime(localtime(&dir_tmp.info[i].st_mtimesec)));
                //strcpy(st2,asctime(localtime(&dir_tmp.info[i].st_ctimesec)));
                st1[strlen(st1)-1]='\0';
                st2[strlen(st2)-1]='\0';
/*
                printf( "%12lld bytes   %s mtime   %s ctime   %s\n",
                           (long long)dir_tmp.info[i].st_size, 
                           st1,
                           st2,
                           dir_tmp.info[i].full_filename
                           );
*/
 printf( "%12lld bytes   %s mtime   %s\n",
                           (long long)dir_tmp.info[i].st_size, 
                           st1,
                           dir_tmp.info[i].full_filename
                           );
        }
        
}

void Upload(DIR_INFO dir_local,DIR_INFO dir_remote,int k_local)
{
	char command[PATH_MAX*2+10];

	if (strncmp(basename(dir_local.info[k_local].full_filename), ".", PATH_MAX)==0) {
		sprintf(command,"%s%s",dir_remote.prefix,dirname(dir_local.info[k_local].full_filename));
		mkdir(command,S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	} else {		
		sprintf(command,"cp -p %s%s %s%s",dir_local.prefix,dir_local.info[k_local].full_filename, dir_remote.prefix,dir_local.info[k_local].full_filename);
		printf("%s\n",command);
		system(command);
	}
}

void Download(DIR_INFO dir_local,DIR_INFO dir_remote,int k_remote)
{
	char command[PATH_MAX*2+10];

	if (strncmp(basename(dir_remote.info[k_remote].full_filename), ".", PATH_MAX)==0) {
		sprintf(command,"%s%s",dir_local.prefix,dirname(dir_remote.info[k_remote].full_filename));
		mkdir(command,S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	} else {
		sprintf(command,"cp -p %s%s %s%s",dir_remote.prefix,dir_remote.info[k_remote].full_filename, dir_local.prefix,dir_remote.info[k_remote].full_filename);
		printf("%s\n",command);
		system(command);
	}
}

void DeleteLocal(DIR_INFO dir_local,int k_local)
{
	char command[PATH_MAX*2+10];
	
	if (strncmp(basename(dir_local.info[k_local].full_filename), ".", PATH_MAX)==0) {
		sprintf(command,"rm -Rf %s%s",dir_local.prefix,dirname(dir_local.info[k_local].full_filename));
		printf("%s\n",command);
		system(command);
	} else {		
		sprintf(command,"%s%s",dir_local.prefix,dir_local.info[k_local].full_filename);
		unlink(command);
		printf("Deleted %s\n",command);
	}
}	

void DeleteRemote(DIR_INFO dir_remote,int k_remote)
{
	char command[PATH_MAX*2+10];
	
	if (strncmp(basename(dir_remote.info[k_remote].full_filename), ".", PATH_MAX)==0) {
		sprintf(command,"rm -Rf %s%s",dir_remote.prefix,dirname(dir_remote.info[k_remote].full_filename));
		printf("%s\n",command);
		system(command);
	} else {
		sprintf(command,"%s%s",dir_remote.prefix,dir_remote.info[k_remote].full_filename);
		unlink(command);
		printf("Deleted %s\n",command);
	}
}	
